#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os
import sys
import argparse
import traceback
import time

import provider
import sourcetraildb as srctrl

import logging
log_format = "%(levelname)s: %(filename)s-%(lineno)d/%(funcName)s: %(message)s"
log_format = "%(levelname)s: %(lineno)d/%(funcName)s: %(message)s"

logging.basicConfig(format=log_format)
log = logging.getLogger("[logdebug]")
log.setLevel(logging.INFO)


class FuncTrailDB:
    def __init__(self, db_path, prov, skiper=None):
        self.db_path = db_path
        self.provider = prov
        if skiper:
            self.skiper = skiper
        else:
            self.skiper = provider.IngoreRE()

        self.file_map = {}
        self.sym_map = {}

        if not srctrl.open(self.db_path):
            log.error(srctrl.getLastError())
            return None

    def __del__(self):
        if not srctrl.close():
            log.error(srctrl.getLastError())

    def clear(self):
        return srctrl.clear()

    def getSupportedDatabaseVersion(self):
        return srctrl.getSupportedDatabaseVersion()

    def clearLastError(self):
        return srctrl.clearLastError()

    def getLastError(self):
        return srctrl.getLastError()

    @classmethod
    def gen_symbol_json(cls, symbol):
        json_str = '{'
        json_str += '"name_delimiter": "",'
        json_str += '"name_elements": '
        # can not use '... {} ...'.format(symbol)
        json_str += '[{ "prefix": "", "name": "' + \
            symbol + '", "postfix": "" } ]'
        json_str += '}'
        return json_str

    def skip_link(self, src, dst):
        # keep ignore -> ignore
        if self.skiper.ignore(dst) and (not self.skiper.ignore(src)):
            return True
        return False

    def __process_files(self):
        for filename in sorted(self.provider.files()):
            fileId = srctrl.recordFile(filename)
            srctrl.recordFileLanguage(fileId, "cpp")
            self.file_map[filename] = fileId
            if len(srctrl.getLastError()) > 0:
                log.error(srctrl.getLastError())
                srctrl.clearLastError()
                log.error(filename)

    def __process_symbols(self):
        for row in sorted(self.provider.symbols()):
            try:
                id, typename, filepath, lineno, column, src, wsrc, dst, wdst = row[:]
                lineno, column = int(lineno), int(column)

                json_str = self.gen_symbol_json(src)
                symId = srctrl.recordSymbol(json_str)
                self.sym_map[wsrc] = symId
                srctrl.recordSymbolDefinitionKind(symId, srctrl.DEFINITION_EXPLICIT)
                srctrl.recordSymbolKind(symId, srctrl.SYMBOL_FUNCTION)
                if filepath:
                    fileId = self.file_map[filepath]
                    srctrl.recordSymbolLocation(symId, fileId, lineno, column,
                                                lineno, column + len(src) - 1)
                if len(srctrl.getLastError()) > 0:
                    log.error(srctrl.getLastError())
                    srctrl.clearLastError()
            except Exception as e:
                log.warning(e)
                log.warning(traceback.format_exc())

    def __add_from_link(self, src):
        try:
            tmp_path = src.split(':')[0]
            tmp = src.split(':')[-1]

            json_str = self.gen_symbol_json(tmp)

            symId = srctrl.recordSymbol(json_str)
            if tmp_path not in self.file_map.keys():
                fileId = srctrl.recordFile(tmp_path)
                srctrl.recordFileLanguage(fileId, "cpp")
                self.file_map[tmp_path] = fileId
            fileId = self.file_map[tmp_path]
            srctrl.recordLocalSymbolLocation(symId, fileId, 1, 1,
                                            1, len(tmp))
            self.sym_map[src] = symId
            srctrl.recordSymbolKind(symId, srctrl.SYMBOL_FUNCTION)
        except Exception as e:
            log.warning(e)

    def __process_links(self, link_ignore=False, link_self=False):
        for row in sorted(self.provider.links()):
            try:
                id, typename, filepath, lineno, column, src, wsrc, dst, wdst = row[:]
                lineno, column = int(lineno), int(column)

                if wsrc not in self.sym_map:
                    self.__add_from_link(wsrc)
                if wdst not in self.sym_map:
                    self.__add_from_link(wdst)

                if not link_ignore:
                    if self.skip_link(src, dst):
                        continue

                if not link_self:
                    if src == dst:
                        continue

                src_id = self.sym_map[wsrc]
                dst_id = self.sym_map[wdst]

                ref_id = srctrl.recordReference(src_id, dst_id, srctrl.REFERENCE_CALL)
                if ref_id == 0:
                    if len(srctrl.getLastError()) > 0:
                        log.error(srctrl.getLastError())
                        srctrl.clearLastError()
                    continue

                if filepath:
                    fileId = self.file_map[filepath]
                    srctrl.recordReferenceLocation(ref_id, fileId, lineno, column,
                                                   lineno, column + len(dst) - 1)
                if len(srctrl.getLastError()) > 0:
                    log.error(srctrl.getLastError())
                    srctrl.clearLastError()
                    log.error(row)
                    log.error("{} {} {}".format(src_id, dst_id, ref_id))
            except Exception as e:
                log.warning(e)
                log.warning(traceback.format_exc())

    def process(self, link_ignore=False, link_self=False):
        srctrl.beginTransaction()
        self.__process_files()
        self.__process_symbols()
        self.__process_links(link_ignore, link_self)
        srctrl.commitTransaction()

def main(args):
    try:
        prov = None
        infiles = args.files
        if args.infmt == 'db':
            dbfile = infiles[0]
            if not os.path.exists(dbfile):
                print("No such file or directory: {}".format(db_name))
                os._exit(-1)

            prov = provider.DBProvider(infiles[0])
        else:
            print("infmt only support db for now")
            os._exit(-1)

        workdir = os.getcwd()
        # after get provider and getcwd
        if args.srcdir:
            os.chdir(args.srcdir)

        db_name = args.output + '.srctrldb'
        if not db_name.startswith('/'):
            db_name = os.path.join(workdir, db_name)
        ftdb = FuncTrailDB(db_name, prov)

        t_start = time.time()
        print("clearing {}...".format(db_name))
        ftdb.clear()
        ftdb.clearLastError()
        print('clear done at {t_use:.2f}s'.format(t_use=(time.time() - t_start)))

        print("start build trail DB...")
        ftdb.process(args.keep_link_ignore, args.keep_link_self)
        print('build {} done'.format(db_name))
        print('build take {t_use:.2f}s'.format(t_use=(time.time() - t_start)))

        if len(ftdb.getLastError()) > 0:
            log.error(ftdb.getLastError())

    except Exception as e:
        print(e)
        print(traceback.format_exc())


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.description = '%(prog)s: SourceTrail DB builder'

    parser.add_argument("-I", "--infmt",
                        help='Input formats(file suffix) are: csv/db/ci/cilist')

    parser.add_argument("-k", "--keep-link-ignore", default=False,
                        help='keep link ignore')

    parser.add_argument("-K", "--keep-link-self", default=False,
                        help='keep link self')

    parser.add_argument("-s", "--srcdir",
                        help='source file path, sourcetrail workdir')

    parser.add_argument("-o", "--output", required=True,
                        help="output prefix, generate output.srctrlprj and output.srctrldb")

    parser.add_argument("files", nargs="+", type=str,
                        help="input files")

    args = parser.parse_args()
    print(args)
    main(args)
